/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.commons.lang3.tuple.Pair;

import edu.ucsd.db.bassarisk.algebra.logical.LogicalOperator;
import edu.ucsd.db.bassarisk.algebra.logical.visitor.ComputeOutputDescriptors;
import edu.ucsd.db.bassarisk.columndef.DataColumnDefinition;
import edu.ucsd.db.bassarisk.exception.ParserException;
import edu.ucsd.db.bassarisk.exception.QueryValidationException;
import edu.ucsd.db.bassarisk.exception.SchemaValidationException;
import edu.ucsd.db.bassarisk.query.ast.DesugarImpliedConjunction;
import edu.ucsd.db.bassarisk.query.ast.PredicateResolutionTransformation;
import edu.ucsd.db.bassarisk.query.binding.ActiveBindings;
import edu.ucsd.db.bassarisk.query.binding.BindingFactory;
import edu.ucsd.db.bassarisk.query.binding.BindingSet;
import edu.ucsd.db.bassarisk.query.binding.BindingTuple;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.BindingCombination;
import edu.ucsd.db.bassarisk.query.binding.Bindings.MapFunctionValueBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.PositionBinding;
import edu.ucsd.db.bassarisk.query.binding.Bindings.RunBinding;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.BindingTreeEdge;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.EstablishingNode;
import edu.ucsd.db.bassarisk.query.bindingtree.visitor.BindingColumnsMappingTraversal;
import edu.ucsd.db.bassarisk.query.bindingtree.visitor.BindingTreeFormattingTraversal;
import edu.ucsd.db.bassarisk.query.bindingtree.visitor.BindingTreeMinimizationTransformation;
import edu.ucsd.db.bassarisk.query.bindingtree.visitor.BindingTupleMapper;
import edu.ucsd.db.bassarisk.query.bindingtree.visitor.MatchBindingCollectionTraversal;
import edu.ucsd.db.bassarisk.query.expression.visitor.BindingTreeBuildTraversal;
import edu.ucsd.db.bassarisk.query.expression.visitor.BooleanSaftyValidationTraversal;
import edu.ucsd.db.bassarisk.query.expression.visitor.BooleanSimplificationTransformation;
import edu.ucsd.db.bassarisk.query.expression.visitor.PositionBindingResolutionTransformation;
import edu.ucsd.db.bassarisk.query.expression.visitor.PositionPredicateExtractionTransformation;
import edu.ucsd.db.bassarisk.query.expression.visitor.PositionPredicateSaftyValidationTraversal;
import edu.ucsd.db.bassarisk.query.expression.visitor.RunClassCollectionTraversal;
import edu.ucsd.db.bassarisk.query.expression.visitor.StructurePredicateSimplificationTransformation;
import edu.ucsd.db.bassarisk.query.parser.Parser;
import edu.ucsd.db.bassarisk.query.path.DSDSchemaInitializeVisitor;
import edu.ucsd.db.bassarisk.query.predicate.PositionPredicates.PositionPredicate;
import edu.ucsd.db.bassarisk.query.predicate.PredicateGuardTable;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.schema.RunClass;
import edu.ucsd.db.bassarisk.schema.Schema;
import edu.ucsd.db.bassarisk.schema.SchemaNode;
import edu.ucsd.db.bassarisk.scoring.AllTokensScoringSignalBranchBuilder;
import edu.ucsd.db.bassarisk.scoring.ScoringScheme;
import edu.ucsd.db.bassarisk.scoring.ScoringSignalDefinition;
import edu.ucsd.db.bassarisk.scoring.ScoringSignalDefinition.AcceptingParameters;
import edu.ucsd.db.bassarisk.scoring.SimpleScoringSignalBranchBuilder;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;
import edu.ucsd.db.bassarisk.util.MultiHashMap;
import edu.ucsd.db.bassarisk.util.MultiMap;

public class QueryProcessor {
  public enum ProcessStage {
    BuildBindingTree,
    BuildExpressionTree,
    Desugar,
    Done,
    InjectScoringSignals,
    MapBindingTuples,
    None,
    Parse,
    ResolvePositionBindings,
    RewritePositionPredicates,
    SimplifyBooleans1,
    SimplifyBooleans2,
    SimplifyStructurePreds,
    ValidateBooleanSafty,
    ValidatePositionPredicateSafty,
    ValidateSchemaOrderedness,
    MapSchemaToData,
    MatchAvalibleColumns,
    ReplacePositionPredicates, MinimizeBindingTree
  }

  private static final Logger LOG = Logger.getLogger(QueryProcessor.class.getName());

  private void parse(final String queryString) throws ParserException {
    LOG.info("Parsing query: " + queryString);
    Parser p = new Parser();
    this.aq = new AstQuery(p.parse(queryString));
  }

  public static QueryProcessor
      process(final String queryString, final Schema schema,
              final ScoringScheme scoringScheme,
              final Collection<DataColumnDefinition> availableColumns)
          throws ParserException, QueryValidationException,
          SchemaValidationException {
    return QueryProcessor.process(queryString, schema, scoringScheme,
                                  availableColumns, ProcessStage.Done,
                                  EnumSet.of(ProcessStage.None));
  }

  public static
      QueryProcessor
      process(final String queryString, final Schema schema,
              final ScoringScheme scoringScheme,
              final Collection<DataColumnDefinition> availableColumns,
              final ProcessStage terminateAt, final EnumSet<ProcessStage> skipStages)
          throws ParserException, QueryValidationException,
          SchemaValidationException {
    QueryProcessor q = new QueryProcessor(schema, scoringScheme, availableColumns);
    q.parse(queryString);
    if (terminateAt == ProcessStage.Parse) { return q; }
    if (!skipStages.contains(ProcessStage.Desugar)) {
      q.desugar();
      LOG.info("Desugar: \n  " + q.formatAst());
    }
    if (terminateAt == ProcessStage.Desugar) { return q; }
    q.resolveExpressions();
    LOG.info("Expression Tree: \n  " + q.formatExpressionTree());
    if (terminateAt == ProcessStage.BuildExpressionTree) { return q; }
    if (!skipStages.contains(ProcessStage.SimplifyBooleans1)) {
      q.simplfyBooleans();
      LOG.info("Booleans Simplified: \n  " + q.formatExpressionTree());
    }
    if (terminateAt == ProcessStage.SimplifyBooleans1) { return q; }
    if (!skipStages.contains(ProcessStage.ValidateBooleanSafty)) {
      q.validateBooleanSafty();
      LOG.info("Booleans Validated: \n  " + q.formatExpressionTree());
    }
    if (terminateAt == ProcessStage.ValidateBooleanSafty) { return q; }
    if (!skipStages.contains(ProcessStage.ValidatePositionPredicateSafty)) {
      q.validatePositionPredicateSafty();
      LOG.info("Position Predicates Validated: \n  " + q.formatExpressionTree());
    }
    if (terminateAt == ProcessStage.ValidatePositionPredicateSafty) { return q; }
    q.validateSchemaOrderedness();
    if (terminateAt == ProcessStage.ValidateSchemaOrderedness) { return q; }
    if (!skipStages.contains(ProcessStage.SimplifyStructurePreds)) {
      q.simplyStructurePreds();
      LOG.info("Position Predicates Simplified: \n  " + q.formatExpressionTree());
    }
    if (terminateAt == ProcessStage.SimplifyStructurePreds) { return q; }
    if (!skipStages.contains(ProcessStage.SimplifyBooleans2)) {
      q.simplfyBooleans();
      LOG.info("Booleans Simplified (2): \n  " + q.formatExpressionTree());
    }
    if (terminateAt == ProcessStage.SimplifyBooleans2) { return q; }
    q.resolvePositionSetBindings();
    LOG.info(q.debugPrintBindings());
    LOG.info(q.debugPrintActiveBindings());
    if (terminateAt == ProcessStage.ResolvePositionBindings) { return q; }
    q.rewritePositionPredicates();
    LOG.info(q.debugPrintGuardTable());
    LOG.info(q.debugPrintPredicates());
    if (terminateAt == ProcessStage.RewritePositionPredicates) { return q; }
    q.buildBindingTree();
    LOG.info("Binding Tree Built: \n" + q.formatBindingTree());
    if (terminateAt == ProcessStage.BuildBindingTree) { return q; }
    if (!skipStages.contains(ProcessStage.MinimizeBindingTree)) {
      q.minimizeBindingTree();
      LOG.info("Binding Tree Minimized");
    }
    if (terminateAt == ProcessStage.MinimizeBindingTree) { return q; }
    q.injectScoringSignals();
    LOG.info(q.debugPrintBindings());
    LOG.info("With Scoring Signals: \n" + q.formatBindingTree());
    if (terminateAt == ProcessStage.InjectScoringSignals) { return q; }
    q.mapBindingTuples();
    LOG.info(q.debugPrintBindingTuples());
    if (terminateAt == ProcessStage.MapBindingTuples) { return q; }
    q.mapSchemaToData();
    LOG.info(q.debugPrintSchemaMap());
    if (terminateAt == ProcessStage.MapSchemaToData) { return q; }
    if (!skipStages.contains(ProcessStage.ReplacePositionPredicates)) {
      q.replacePositionPredicates();
      LOG.info("Position predicates replaced");
    }
    if (terminateAt == ProcessStage.ReplacePositionPredicates) { return q; }
    q.matchAvalibleColumns();
    if (terminateAt == ProcessStage.MatchAvalibleColumns) { return q; }
    return q;
  }

  private void minimizeBindingTree() {
//    BindingTreeMinimizationTransformation visitor = new BindingTreeMinimizationTransformation();
//    this.bq.applyTransformation(visitor, null);
  }

  private void replacePositionPredicates() {
    BindingTreeEdge answerRoot = this.bq.getAnswerRoot();
    Collection<PositionPredicate> predicates = answerRoot.predicates();
    predicates.addAll(this.extractedPsnPredicates);
  }

  private void matchAvalibleColumns() throws QueryValidationException {
    ValidationTraversalLog vtl = new ValidationTraversalLog();
    BindingColumnsMappingTraversal visitor =
        new BindingColumnsMappingTraversal(this.schemaDataMap, this.bindingTuples, vtl);
    vtl.checkLog(LOG);
    LogicalOperator plan = this.bq.applyVisitor(visitor, Pair.of(false, this.availableColumns));
    plan.accept(new ComputeOutputDescriptors(), null);
    System.out.println(plan.prettyPrint());
  }

  private String debugPrintSchemaMap() {
    return this.schemaDataMap.toString();
  }

  private final Collection<DataColumnDefinition> availableColumns;

  private void mapSchemaToData() throws SchemaValidationException {
    ValidationTraversalLog vtl = new ValidationTraversalLog();
    DSDSchemaInitializeVisitor schemaInit =
        new DSDSchemaInitializeVisitor(this.schema, vtl);
    for (DataColumnDefinition column : this.availableColumns) {
      RunClass rootClass = new RunClass(Constants.IndexRootRunName);
      rootClass.initailize(this.schema);
      column.applyVisitor(schemaInit, rootClass);
      column.initialize(schema);
      DataSequenceClass aggregationContextClass = null;
      if (column.getAggregationContext() != null) {
        aggregationContextClass = column.getAggregationContext().getSequenceClass();
      } else {
        aggregationContextClass = column.getAggregationTarget().getSequenceClass();
      }
      for (SchemaNode schemaNode : aggregationContextClass.getRefinement()) {
        this.schemaDataMap.appendTo(schemaNode, column);
      }
    }
  }

  private ActiveBindings activeBindings;
  private AstQuery aq;
  private final BindingFactory bindingFactory;
  private final BindingSet bindings;
  private final Map<BindingTreeEdge, BindingTuple> bindingTuples;
  private BindingTreeQuery bq;
  private ExpressionTreeQuery eq;
  private final Collection<PositionPredicate> extractedPsnPredicates;
  private final List<Binding> outputBindings;
  private final Schema schema;
  private final ScoringScheme scoringScheme;
  private final MultiMap<SchemaNode, DataColumnDefinition> schemaDataMap;

  private QueryProcessor(final Schema schema, ScoringScheme scoringScheme,
                         Collection<DataColumnDefinition> availableColumns) {
    this.bindingTuples = new HashMap<BindingTreeEdge, BindingTuple>();
    this.schemaDataMap = new MultiHashMap<SchemaNode, DataColumnDefinition>();
    this.extractedPsnPredicates = new ArrayList<PositionPredicate>();
    this.outputBindings = new ArrayList<Binding>();
    this.bindings = new BindingSet();
    this.bindingFactory = new BindingFactory(this.bindings);
    this.schema = schema;
    this.scoringScheme = scoringScheme;
    this.availableColumns = availableColumns;
  }

  private void buildBindingTree() throws SchemaValidationException,
      QueryValidationException {
    ValidationTraversalLog vtl = new ValidationTraversalLog();
    BindingTreeBuildTraversal builder = new BindingTreeBuildTraversal(vtl);
    List<BindingTreeEdge> roots = this.eq.applyVisitor(builder, null);
    vtl.checkLog(LOG);
    if (builder.getAnswerLead() == null) {
      String msg = "Query must have exactly one 'Find' expression to be valid.";
      throw new QueryValidationException(msg);
    }
    Binding rootBinding =
        this.bindingFactory.createStructureBinding(RunClass.INDEX_ROOT);
    RunClass.INDEX_ROOT.initailize(this.schema);
    EstablishingNode root = new EstablishingNode(rootBinding);
    for (BindingTreeEdge topLevelEdge : roots) {
      root.addChild(topLevelEdge);
    }
    BindingTreeEdge rootEdge = new BindingTreeEdge(root);
    rootEdge.functions().add(EdgeFunction.Values);
    this.bq = new BindingTreeQuery(rootEdge, builder.getAnswerLead());
  }

  private String debugPrintActiveBindings() {
    StringBuilder sb = new StringBuilder();
    this.debugPrintActiveBindings(sb);
    return sb.toString();
  }

  private void debugPrintActiveBindings(final StringBuilder sb) {
    for (String runClassName : this.activeBindings.getBoundRunClassNames()) {
      sb.append(runClassName).append(": ");
      for (Binding binding : this.activeBindings.getBindings(runClassName)) {
        sb.append("$").append(binding.getId()).append(" ");
      }
      sb.append("\n");
    }
  }

  private String debugPrintBindings() {
    StringBuilder sb = new StringBuilder();
    this.debugPrintBindings(sb);
    return sb.toString();
  }

  private void debugPrintBindings(final StringBuilder sb) {
    for (Binding binding : this.bindings) {
      sb.append(binding.toString()).append("\n");
    }
  }

  private String debugPrintBindingTuples() {
    StringBuilder sb = new StringBuilder();
    sb.append("Binding Tuples:\n");
    for (BindingTreeEdge edge : this.bindingTuples.keySet()) {
      sb.append("  ");
      sb.append(edge.toString());
      for (Binding binding : this.bindingTuples.get(edge).bindings()) {
        sb.append("\n    ");
        sb.append(binding.toString());
      }
      sb.append("\n");
    }
    return sb.toString();
  }

  private String debugPrintGuardTable() {
    StringBuilder sb = new StringBuilder();
    for (String runClassName : this.activeBindings.getBoundRunClassNames()) {
      sb.append(new PredicateGuardTable(this.bindings, runClassName));
    }
    return sb.toString();
  }

  private String debugPrintPredicates() {
    StringBuilder sb = new StringBuilder().append("Simplified Predicates: \n");
    for (PositionPredicate predicate : this.extractedPsnPredicates) {
      sb.append(predicate.toString()).append("\n");
    }
    return sb.toString();
  }

  private void desugar() {
    this.aq.applyTransformation(new DesugarImpliedConjunction(), null);
  }

  private List<PositionBinding> findPositiveMatchBindings() {
    MatchBindingCollectionTraversal visitor = new MatchBindingCollectionTraversal();
    List<PositionBinding> matchBindings = new ArrayList<PositionBinding>();
    this.bq.applyVisitor(visitor, matchBindings);
    return matchBindings;
  }

  public String format() {
    if (this.eq == null) {
      return this.formatAst();
    } else if (this.bq == null) {
      return this.formatExpressionTree();
    } else {
      return this.formatBindingTree();
    }
  }

  public String formatAst() {
    StringBuilder sb = new StringBuilder();
    this.aq.applyVisitor(new QueryFormattingTraversal(), sb);
    return sb.toString();
  }

  public String formatBindingTree() {
    StringBuilder sb = new StringBuilder();
    this.bq.applyVisitor(new BindingTreeFormattingTraversal(),
                         new BindingTreeFormattingTraversal.Args(sb));
    return sb.toString();
  }

  public String formatExpressionTree() {
    StringBuilder sb = new StringBuilder();
    this.eq.applyVisitor(new QueryFormattingTraversal(), sb);
    return sb.toString();
  }

  public Schema getSchema() {
    return this.schema;
  }

  private void injectScoringSignals() throws QueryValidationException,
      SchemaValidationException {
    Collection<ScoringSignalDefinition> signalDefs =
        scoringScheme.signalDefintions();
    // Keep track of all omega-signals (global or answer-granularity)
    List<Binding> omegaSignalBindings = new ArrayList<Binding>();
    // Keep track of all alpha-signals (specific for each atomic-match)
    Map<PositionBinding, List<Binding>> alphaSignalBindings =
        new HashMap<PositionBinding, List<Binding>>();
    // Final all atomic match bindings
    List<PositionBinding> atomicMatchBindings = this.findPositiveMatchBindings();
    for (PositionBinding atomicMatchBinding : atomicMatchBindings) {
      alphaSignalBindings.put(atomicMatchBinding, new ArrayList<Binding>());
    }
    for (ScoringSignalDefinition signalDef : signalDefs) {
      // Nodes in the query tree that will be useful for this process
      BindingTreeEdge answerRoot = this.bq.getAnswerRoot();
      BindingTreeEdge queryRoot = this.bq.getQueryRoot();
      boolean inAnswerContext =
          signalDef.hasParameter(AcceptingParameters.ContextNode);
      BindingTreeEdge insertRootEdge = inAnswerContext ? answerRoot : queryRoot;
      EstablishingNode insertRootNode = (EstablishingNode) insertRootEdge.tailNode();
      if (!inAnswerContext) {
        EstablishingNode answerRootNode = (EstablishingNode) answerRoot.tailNode();
        RunBinding answerBinding =
            (RunBinding) answerRootNode.getEstablishedBinding();
        RunBinding rootBinding = (RunBinding) insertRootNode.getEstablishedBinding();
        signalDef.expandContextsPath(this.schema.sequenceContainmentComparator(),
                                     (RunClass) answerBinding.boundSequenceClass(),
                                     (RunClass) rootBinding.boundSequenceClass());
      }
      signalDef.initailize(this.schema, insertRootNode.getEstablishedBinding()
          .boundSequenceClass());
      boolean tokenParam =
          signalDef.hasParameter(AcceptingParameters.AllTokens)
              || signalDef.hasParameter(AcceptingParameters.FilteredTokens);
      boolean tokenFiltered =
          signalDef.hasParameter(AcceptingParameters.FilteredTokens);
      if (tokenParam) {
        // Then do once for each token in the query
        for (PositionBinding matchBinding : atomicMatchBindings) {
          List<Binding> alphaSignals = alphaSignalBindings.get(matchBinding);
          if (tokenFiltered && inAnswerContext) {
            // TODO insertScoringSignalAtFiltered(insertionRoot, signalDef,
            // tokenNode);
          } else {
            alphaSignals.add(this.insertLocalScoringSignal(insertRootEdge,
                                                           signalDef, matchBinding));
          }
        }
      } else {
        omegaSignalBindings.add(this.insertGlobalScoringSignal(insertRootEdge,
                                                               signalDef));
      }
    }
    // All the scoring signals are inserted into the tree-pattern query. Now we
    // generate the scoring plan as a sequence of bindings. Like the predicates
    // that are already extracted, the scoring plan bindings are not held in the
    // tree pattern query but will be inserted as the query plan is generated.
    // .
    // Start by adding computations to compute the alpha scores for atomic
    // matches.
    Map<Binding, MapFunctionValueBinding> matchToScoreBindingMap =
        new HashMap<Binding, MapFunctionValueBinding>();
    for (Binding matchBinding : atomicMatchBindings) {
      List<Binding> alphaInput = alphaSignalBindings.get(matchBinding);
      MapFunctionValueBinding scoreBinding =
          this.bindingFactory.createMapFunctionBinding(Constants.ScoreAlpha,
                                                       alphaInput);
      this.bq.getAnswerRoot().computations().add(scoreBinding);
      matchToScoreBindingMap.put(matchBinding, scoreBinding);
    }
    // To proceed, we need to get the atomic matching bindings into a useful
    // order for generating the scoring plan. Start by computing the depth of
    // each binding (according to the tree of product/union bindings).
    Map<Integer, Collection<Binding>> atomicMatchDepths =
        new HashMap<Integer, Collection<Binding>>();
    for (Binding matchBinding : atomicMatchBindings) {
      Binding binding = matchBinding;
      int depth = 0;
      while (binding.getParent() != null) {
        binding = binding.getParent();
        depth++;
      }
      if (!atomicMatchDepths.containsKey(depth)) {
        atomicMatchDepths.put(depth, new ArrayList<Binding>());
      }
      atomicMatchDepths.get(depth).add(matchBinding);
    }
    // Find the maximum depth
    int maxDepth = 0;
    for (Integer depth : atomicMatchDepths.keySet()) {
      if (depth.intValue() > maxDepth) {
        maxDepth = depth;
      }
    }
    for (int depth = maxDepth; depth > 0; --depth) {
      Collection<Binding> matchesAtDepth = atomicMatchDepths.get(depth);
      while (matchesAtDepth.size() > 0) {
        Binding binding = matchesAtDepth.iterator().next();
        BindingCombination parent = binding.getParent();
        String functionName;
        functionName =
            binding.inProduct() ? Constants.ScoreProduct : Constants.ScoreUnion;
        List<MapFunctionValueBinding> scoreBindingInputs =
            new ArrayList<MapFunctionValueBinding>();
        for (Binding child : parent.getCombinedBindings()) {
          if (!matchesAtDepth.remove(child)) {
            String msg = "Internal Error";
            throw new RuntimeException(msg);
          }
          scoreBindingInputs.add(matchToScoreBindingMap.get(child));
        }
        MapFunctionValueBinding parentScoreBinding =
            this.bindingFactory.createMapFunctionBinding(functionName,
                                                         scoreBindingInputs);
        this.bq.getAnswerRoot().computations().add(parentScoreBinding);
        matchToScoreBindingMap.put(parent, parentScoreBinding);
        if (!atomicMatchDepths.containsKey(depth - 1)) {
          atomicMatchDepths.put(depth - 1, new ArrayList<Binding>());
        }
        atomicMatchDepths.get(depth - 1).add(parent);
      }
    }
    // Finally, we do depth 0 as a special case.
    Collection<Binding> matchesAtDepthZero = atomicMatchDepths.get(0);
    List<MapFunctionValueBinding> scoreBindingInputs =
        new ArrayList<MapFunctionValueBinding>();
    for (Binding depthZero : matchesAtDepthZero) {
      scoreBindingInputs.add(matchToScoreBindingMap.get(depthZero));
    }
    MapFunctionValueBinding product = this.bindingFactory
        .createMapFunctionBinding(Constants.ScoreProduct, scoreBindingInputs);
    this.bq.getAnswerRoot().computations().add(product);
    omegaSignalBindings.add(product);
    MapFunctionValueBinding omegaBinding =
        this.bindingFactory.createMapFunctionBinding(Constants.ScoreOmega,
                                                     omegaSignalBindings);
    this.bq.getQueryRoot().computations().add(omegaBinding);
    this.outputBindings.add(omegaBinding);
    // Take this opportunity to initialize any uninitialized sequence classes in
    // the binding table.
    for (Binding binding : this.bindings) {
      DataSequenceClass dsc = binding.boundSequenceClass();
      if (!dsc.isInitialized()) {
        dsc.initailize(this.schema);
      }
    }
  }

  private Binding insertGlobalScoringSignal(final BindingTreeEdge insertionRoot,
                                            final ScoringSignalDefinition signalDef) {
    SimpleScoringSignalBranchBuilder builder =
        new SimpleScoringSignalBranchBuilder(this.bindingFactory);
    return signalDef.applyVisitor(builder,
                                  (EstablishingNode) insertionRoot.tailNode());
  }

  private Binding insertLocalScoringSignal(final BindingTreeEdge insertionRoot,
                                           final ScoringSignalDefinition signalDef,
                                           final PositionBinding tokenMatchBinding) {
    AllTokensScoringSignalBranchBuilder builder =
        new AllTokensScoringSignalBranchBuilder(this.bindingFactory,
                                                tokenMatchBinding);
    return signalDef.applyVisitor(builder,
                                  (EstablishingNode) insertionRoot.tailNode());
  }

  private void mapBindingTuples() {
    BindingTupleMapper visitor = new BindingTupleMapper(this.bindingTuples);
    this.bq.applyVisitor(visitor, null);
  }

  private void resolveExpressions() throws QueryValidationException {
    PredicateResolutionTransformation tc = new PredicateResolutionTransformation();
    ValidationTraversalLog args = new ValidationTraversalLog();
    this.eq = new ExpressionTreeQuery(this.aq.applyVisitor(tc, args));
    args.checkLog(LOG);
  }

  private QueryProcessor resolvePositionSetBindings()
      throws QueryValidationException {
    ValidationTraversalLog vtl = new ValidationTraversalLog();
    PositionBindingResolutionTransformation bindingResolver =
        new PositionBindingResolutionTransformation(this.bindings,
                                                    this.bindingFactory,
                                                    this.schema, vtl);
    PositionBindingResolutionTransformation.Return visitReturn;
    visitReturn = this.eq.applyTransformation(bindingResolver, null);
    vtl.checkLog(LOG);
    this.activeBindings = visitReturn.activeBindings;
    return this;
  }

  private QueryProcessor rewritePositionPredicates() throws QueryValidationException {
    PositionPredicateExtractionTransformation extractor =
        new PositionPredicateExtractionTransformation(this.extractedPsnPredicates,
                                                      this.bindings);
    this.eq.applyTransformation(extractor, null);
    return this;
  }

  private void simplfyBooleans() {
    this.eq.applyTransformation(new BooleanSimplificationTransformation(), null);
  }

  private QueryProcessor simplyStructurePreds() throws QueryValidationException {
    assert this.schema != null;
    ValidationTraversalLog vtl = new ValidationTraversalLog();
    StructurePredicateSimplificationTransformation transform =
        new StructurePredicateSimplificationTransformation(this.schema, vtl);
    this.eq.applyTransformation(transform, null);
    vtl.checkLog(LOG);
    return this;
  }

  private void validateBooleanSafty() throws QueryValidationException {
    ValidationTraversalLog args = new ValidationTraversalLog();
    this.eq.applyTransformation(new BooleanSaftyValidationTraversal(), args);
    args.checkLog(LOG);
  }

  private void validatePositionPredicateSafty() throws QueryValidationException {
    ValidationTraversalLog args = new ValidationTraversalLog();
    this.eq.applyVisitor(new PositionPredicateSaftyValidationTraversal(), args);
    args.checkLog(LOG);
  }

  private QueryProcessor validateSchemaOrderedness()
      throws QueryValidationException, SchemaValidationException {
    List<RunClass> queryRunClassList = new ArrayList<RunClass>();
    this.eq.applyVisitor(new RunClassCollectionTraversal(), queryRunClassList);
    // Find a set of only the class names mentioned
    Set<String> mentionedRunClassNamesSet = new HashSet<String>();
    for (RunClass runClass : queryRunClassList) {
      mentionedRunClassNamesSet.add(runClass.getName());
      runClass.initailize(schema);
    }
    return this;
  }
}
